#include "vp.h"
#include "htable.h"
#include <cstdlib>
#include <fstream>
#include <iostream>
#include <cmath>
using std::pow;
using std::exp;
using std::rand;
using std::ofstream;
using std::ifstream;
using std::endl;
using std::cout;
using std::ios;

#define sign(x) ((x)<0?(-1):(1))

class MapPvectorInt : public htable<vector*,int>{
    int hashfunc(vector* const & v)const{
        long vv=(long)v;
        return vv>>3;
    }
};

real linear(vector* x, vector* y, float p){
    return x->dot(*y);
}

real poly(vector* x, vector* y, float p){
    real s=x->dot(*y);
    int d=int(p);
    return pow(s+1.0,d);
}

real hpoly(vector* x, vector* y, float p){
    real s=x->dot(*y);
    int d=int(p);
    return pow(s,d);
}

real rbf(vector* x, vector* y, float p){
    real s=x->dot(*x)+y->dot(*y)-2*x->dot(*y);
    return exp(-p*s);
}

kernel_t perceptron::kernels[4]={linear,poly,hpoly,rbf};

real perceptron::similarity(vector* x, const Array<int>& sv, kernel_t K, float p){
    int i;
    int len=sv.size();
    real ret=0.0;
    for(i=0; i<len; i++){
        vector* svi=svectors[sv[i]];
        ret+=svi->getClass()*K(x,svi,p);
    }
    return ret;
}

void perceptron::shuffle(Array<vector*>& a){
    int r,i;
    vector* t;
    for(i=a.size()-1; i>0; i--){
        r=rand()%(i+1);
        t=a[r]; a[r]=a[i]; a[i]=t;
    }
}

perceptron::perceptron(){
    kernel=linear;
    param=0;
    margin=0;
    bpm=1;
}

perceptron::~perceptron(){
    for(int i=svectors.size()-1; i>=0; i--)
        delete svectors[i];
}

void perceptron::set(int ktype,float p,float m,int b){
    kernel=kernels[ktype];
    param=p;
    margin=m;
    bpm=b;
}

void perceptron::train(Array<vector*>& trainset, int T){
    if(kernel==linear)
        trainlinear(trainset,T);
    else
        trainnonlinear(trainset,T);
}

void perceptron::trainnonlinear(Array<vector*>& trainset, int T){
    int i,j,n,t,y,cnt,z;
    int len=trainset.size();
    Array<int> sv;
    Array<int> c;
    perceptrons.clear();
    votes.clear();
    MapPvectorInt m;
    MapPvectorInt::entry* e;

    vector* zero=new vector();
    cnt=0;
    m.put(zero,cnt++);
    svectors.push(zero);

    for(i=0; i<bpm; i++){
        sv.clear();
        c.clear();
        n=0;
        sv.push(0);
        for(t=0; t<T; t++){
            for(j=0; j<len; j++){
                vector* x = trainset[j];
                y=x->getClass();
                real sim = y*similarity(x,sv,kernel,param);
                if (sim > margin)
                    n++;
                else{
                    e=m.get(x);
                    if(e)
                        z=e->getValue();
                    else{
                        z=cnt;
                        m.put(x,cnt++);
                        svectors.push(x);
                    }
                    sv.push(z);
                    c.push(n);
                    n=1;
                }
            }
        }
        c.push(n);
        perceptrons.push(sv);
        votes.push(c);
        shuffle(trainset);
    }
}

void perceptron::trainlinear(Array<vector*>& trainset, int T){
    int i,j,n,t,y,cnt,z;
    int len=trainset.size();
    Array<int> sv;
    Array<int> c;
    perceptrons.clear();
    votes.clear();
    MapPvectorInt m;
    MapPvectorInt::entry* e;
    cnt=0;

    vector* zero=new vector();
    cnt=0;
    m.put(zero,cnt++);
    svectors.push(zero);

    for(i=0; i<bpm; i++){
        vector v;
        sv.clear();
        c.clear();
        n=0;
        sv.push(0);
        for(t=0; t<T; t++){
            for(j=0; j<len; j++){
                vector* x = trainset[j];
                y=x->getClass();
                real sim = y*v.dot(*x);
                if (sim > margin)
                    n++;
                else{
                    v.update(x);
                    e=m.get(x);
                    if(e)
                        z=e->getValue();
                    else{
                        z=cnt;
                        m.put(x,cnt++);
                        svectors.push(x);
                    }
                    sv.push(z);
                    c.push(n);
                    n=1;
                }
            }
        }
        c.push(n);
        perceptrons.push(sv);
        votes.push(c);
        shuffle(trainset);
    }
}

void perceptron::test(const Array<vector*>& testset, Array<real>& predv, Array<real>& preda){
    int i,j,k;
    int len=testset.size();
    real* cache = new real[svectors.size()];
    predv.clear();
    preda.clear();
    for(i=0; i<len; i++){
        for(j=0; j<svectors.size(); j++)
            cache[j]=kernel(testset[i],svectors[j],param);
        real s = 0.0;
        real a = 0.0;
        for(j=0; j<bpm; j++){
            real m = 0.0;
            int svs = perceptrons[j].size();
            for(k=0; k<svs; k++){
                int c=votes[j][k];
                int y=svectors[perceptrons[j][k]]->getClass();
                m+=y*cache[perceptrons[j][k]];
                s+=c*sign(m);
                a+=c*m;
            }
        }
        predv.push(s);
        preda.push(a);
    }
    delete[] cache;
}

void perceptron::read(const char* file){
    int i,j,k,ktype,v,len1,len2,ind,label;
    real val;
    Array<int> c,ai;
    Array<real> av;
    Array<int> svi;
    int cnt;

    ifstream ifs(file);
    ifs>>ktype>>param>>margin>>bpm>>cnt;
    kernel=kernels[ktype];

    for(i=0; i<cnt; i++){
        ifs>>len2>>label;
        ai.clear(); av.clear();
        for(k=0; k<len2; k++){
            ifs >> ind >> val;
            ai.push(ind); av.push(val);
        }
        svectors.push(new vector(label,ai,av));
    }

    for(i=0; i<bpm; i++){
        c.clear();
        ifs >> len1;
        for(j=0; j<len1; j++){
            ifs >> v; 
            c.push(v);
        }
        votes.push(c);
        svi.clear();
        for(j=0; j<len1; j++){
            ifs >> k;
            svi.push(k);
        }
        perceptrons.push(svi);
    }
    ifs.close();
}

void perceptron::write(const char* file){
    int i,j,k,len1,len2,ind;
    real val;
    ofstream ofs(file);
    MapPvectorInt m;
    Array<vector*> minv;

    for(i=0; i<4; i++)
        if(kernel==kernels[i])
            ofs<<i<<" ";
    ofs<<param<<" "<<margin<<" "<<bpm<<" "<<svectors.size() <<endl;

    for(i=0; i<svectors.size(); i++){
        vector* sv=svectors[i];
        len2=sv->size();
        ofs<<len2<<" "<<sv->getClass()<<" ";
        for(k=0; k<len2; k++){
            sv->at(k, ind, val);
            ofs<<ind<<" "<<val<<" ";
        }
        ofs << endl;
    }

    for(i=0; i<bpm; i++){
        len1=votes[i].size();
        ofs<<len1<<endl;
        for(j=0; j<len1; j++)
            ofs<<votes[i][j]<<" ";
        ofs<<endl;
        for(j=0; j<len1; j++)
            ofs<<perceptrons[i][j]<<" ";
        ofs<<endl;
    }
    ofs.close();
}
